/*
 * Copyright 2010 Christian Schindelhauer, Peter Thiemann, Faisal Aslam, Luminous Fennell and Gidon Ernst.
 * All Rights Reserved.
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER
 * 
 * This code is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License version 3
 * only, as published by the Free Software Foundation.
 * 
 * This code is distributed in the hope that it will be useful, but
 * WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
 * General Public License version 3 for more details (a copy is
 * included in the LICENSE file that accompanied this code).
 * 
 * You should have received a copy of the GNU General Public License
 * version 3 along with this work; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
 * 02110-1301 USA
 * 
 * Please contact Faisal Aslam 
 * (aslam AT informatik.uni-freibug.de or studentresearcher AT gmail.com)
 * if you need additional information or have any questions.
 */
package takatuka.offlineGC.DFA.logic;

import java.util.*;
import takatuka.classreader.dataObjs.*;
import takatuka.classreader.logic.util.*;
import takatuka.offlineGC.DFA.dataObjs.*;
import takatuka.offlineGC.DFA.dataObjs.attribute.*;
import takatuka.offlineGC.DFA.dataObjs.flowRecord.*;
import takatuka.offlineGC.DFA.dataObjs.functionState.*;
import takatuka.verifier.dataObjs.*;
import takatuka.verifier.dataObjs.attribute.*;
import takatuka.offlineGC.DFA.dataObjs.virtualThreading.*;
import takatuka.offlineGC.DFA.logic.partialInstrOrder.*;

import takatuka.optimizer.VSS.logic.preCodeTravers.*;
import takatuka.verifier.logic.factory.*;
import takatuka.verifier.logic.DFA.*;

/**
 * 
 * Description:
 * <p>
 *
 * </p> 
 * @author Faisal Aslam
 * @version 1.0
 */
public class GCDataFlowAnalyzer extends DataFlowAnalyzer {

    private static final GCDataFlowAnalyzer myObj = new GCDataFlowAnalyzer();
    private static HashSet<String> methodExecuted = new HashSet<String>();
    private static final String THREAD_RUN_METHOD_NAME = "run";
    private static final String THREAD_RUN_METHOD_DESC = "()V";
    private static final String EMPTY_CONSTRUCTOR_NAME = "<init>";
    private static final String EMPTY_CONSTRUCTOR_DESC = "()V";
    private OrderManagerForFieldInstrs getfieldAndLoadInstrContr = OrderManagerForFieldInstrs.getInstanceOf();
    /**
     * Keep the set of method started but not finished in the list.
     */
    private HashSet<FunctionStateKey> currentlyMethodInExecution = new HashSet<FunctionStateKey>();

    protected GCDataFlowAnalyzer() {
    }

    public static GCDataFlowAnalyzer getInstanceOf() {
        return myObj;
    }

    public static void addMethodExecuted(MethodInfo method) {
        methodExecuted.add(ChangeCodeForReducedSizedLV.createKey(method));
    }

    public static void clearMethodExecuted() {
        methodExecuted.clear();
    }

    public static boolean methodAlreadyExecuted(MethodInfo method) {
        return methodExecuted.contains(ChangeCodeForReducedSizedLV.createKey(method));
    }

    public HashSet<FunctionStateKey> getMethodsInExecution() {
        return currentlyMethodInExecution;
    }

    /*
    @Override
    public void executeNonCatchedExceptions(MethodInfo currentMethod,
    HashSet<Integer> catchedException, Frame frame,
    Vector methodCallingParam) throws Exception {
    //do nothing currently
    }
    
    @Override
    protected void executeRunTimeException(VerificationInstruction inst,
    MethodInfo currentMethod,
    Frame frame, Vector methodCallingParam,
    HashSet exceptionTableEntriedUsed) throws Exception {
    //do nothing currently.
    }*/
    @Override
    protected void executeRunTimeExceptionHelper(VerificationInstruction inst,
            MethodInfo currentMethod,
            Frame frame, Vector methodCallingParam, Vector<Long> targetInstrIDs,
            Vector<Integer> execptionClassesThisPointer) throws Exception {
        if (ExceptionHandler.getInstanceOf().alreadyHandledInstr(inst.getInstructionId())) {
            return;
        }
        ExceptionHandler.getInstanceOf().addHandledInstr(inst.getInstructionId());
        if (true) {
            super.executeRunTimeExceptionHelper(inst, currentMethod, frame, methodCallingParam,
                    targetInstrIDs, execptionClassesThisPointer);
            return;
        }
        /**
         * loop through all the target instr ids. These are the first instructions
         * generated by
         */
        ClassFile currentClassFun = ClassFile.currentClassToWorkOn;
        BytecodeVerifier bcLastFun = DataFlowAnalyzer.getLastSavedBytecodeInterpreter();

        for (int loop = 0; loop < targetInstrIDs.size(); loop++) {
            VerificationFrameFactory frameFactory = VerificationPlaceHolder.getInstanceOf().getFactory();
            OperandStack stack = frameFactory.createOperandStack(frame.getOperandStack().getMaxSize());
            int refType = execptionClassesThisPointer.elementAt(loop);
            GCType type = (GCType) frameFactory.createType(refType, true, -1);
            HashSet<TTReference> references = type.getReferences();
            Iterator<TTReference> it = references.iterator();
            int runNameCPIndex = oracle.getUTF8InfoIndex(EMPTY_CONSTRUCTOR_NAME);
            int runDescCPIndex = oracle.getUTF8InfoIndex(EMPTY_CONSTRUCTOR_DESC);
            String methodKey = MethodInfo.createKey(runDescCPIndex, runNameCPIndex);
            while (it.hasNext()) {
                TTReference reference = it.next();
                if (reference.getClassThisPointer() == Type.NULL) {
                    DataFlowAnalyzer.Debug_print("WARNING: Cannot call a thread run ",
                            "function with a NULL pointer\n");
                    DataFlowAnalyzer.Debug_print1("WARNING: it could produce NullPointerException during run time");
                    continue;
                }
                ClassFile cFile = InvokeInstrs.methodClassLookup(reference.getClassThisPointer(), methodKey);
                if (cFile == null) {
                    continue;
                }
                MethodInfo method = (MethodInfo) oracle.getMethodOrField(cFile,
                        EMPTY_CONSTRUCTOR_NAME, EMPTY_CONSTRUCTOR_DESC, true);
                if (method == null || method.getCodeAtt() == null) {
                    continue;
                }

                //Miscellaneous.println("going to execute "+oracle.getMethodString(method));
                int locMaxSize = method.getCodeAtt().getMaxLocals().intValueUnsigned();
                if (cFile.getFullyQualifiedClassName().contains("jvmTestCases.ExceptionsRelatedOpcodes.testException")) {
                    Miscellaneous.println("stop here 8232");
                }
                //save previous information
                ClassFile currentClass = ClassFile.currentClassToWorkOn;
                BytecodeVerifier bcLast = DataFlowAnalyzer.getLastSavedBytecodeInterpreter();
                ClassFile.currentClassToWorkOn = cFile;

                LocalVariables loc = InitializeFirstInstruction.createLocalVariablesOfFirstInstruction(locMaxSize,
                        EMPTY_CONSTRUCTOR_DESC, false, type);
                execute(method, loc.getAll(), null);
                //restore previous information
                bcLast.initilizeHelperClasses();
                ClassFile.currentClassToWorkOn = currentClass;
            }
            //restore previous information
            bcLastFun.initilizeHelperClasses();
            ClassFile.currentClassToWorkOn = currentClassFun;
            stack.push(type);
            Vector<Long> dummy = new Vector();
            dummy.addElement(targetInstrIDs.elementAt(loop));
            frame.updateFrame(stack);
            workAfterInstructionExecuted(currentMethod, frame, dummy, inst,
                    currentMethod.getInstructions(), methodCallingParam);
        }
    }

    /**
     * If the method called is start on the thread class
     * then its time to start a new thread.
     * 
     * @param method
     * @param localVariables
     * @param lastInstrExecuted
     * @return
     */
    private boolean addNewThreadToBeStarted(MethodInfo method, Vector localVariables,
            VerificationInstruction lastInstrExecuted) {
        String methodStr = oracle.getMethodOrFieldString(method);
        boolean ret = false;


        TreeSet<ClassFile> methodClasses = new TreeSet<ClassFile>();
        if (!methodStr.equals("java.lang.Thread.start()V")) {
            return ret;
        }
        //time to start a new thread.
        ret = true;
        GCType threadObjType = (GCType) localVariables.elementAt(0);
        HashSet<TTReference> threadClassesThisSet = threadObjType.getReferences();
        Iterator<TTReference> it = threadClassesThisSet.iterator();
        int runNameCPIndex = oracle.getUTF8InfoIndex(THREAD_RUN_METHOD_NAME);
        int runDescCPIndex = oracle.getUTF8InfoIndex(THREAD_RUN_METHOD_DESC);
        String methodKey = MethodInfo.createKey(runDescCPIndex, runNameCPIndex);
        while (it.hasNext()) {
            TTReference reference = it.next();
            if (reference.getClassThisPointer() == Type.NULL) {
                DataFlowAnalyzer.Debug_print1("WARNING: Cannot call a thread run "
                        + "function with a NULL pointer\n");
                DataFlowAnalyzer.Debug_print1("WARNING: it could produce NullPointerException during run time");
                continue;
            }
            methodClasses.add(InvokeInstrs.methodClassLookup(reference.getClassThisPointer(), methodKey));
        }
        VirtualThreadController vContr = VirtualThreadController.getInstanceOf();
        Iterator<ClassFile> methodClassesIt = methodClasses.iterator();
        while (methodClassesIt.hasNext()) {
            ClassFile cFile = methodClassesIt.next();
            MethodInfo runMethod = (MethodInfo) oracle.getMethodOrField(cFile,
                    THREAD_RUN_METHOD_NAME, THREAD_RUN_METHOD_DESC, true);
            vContr.createVirtualThread(threadObjType, runMethod, (GCInstruction) lastInstrExecuted);
            Debug_print("add new thread. Latest Virtual Controller = ", vContr);
        }

        return ret;
    }

    /**
     * If the method is already been invoked at least once before then it is not invoked again.
     * There are two cases
     *
     * case 1: Method is still under execution. If so than a dummy return is added in the caller stack
     * and the call is returned.
     * **** Note that later on this dummy return must be replace with the actual return when
     * the original method is returns.
     * Furthermore, if the method is native (has no codeAttribute) then it is also executed virtually.
     *
     * case 2: In case the method had eneded its execution then the return value of the previous call
     * is saved in the caller return.
     *
     * @param methodputField
     * @param localVariables
     * @param callerStack
     * @return
     * true if the method is executed virtually otherwise return false.
     */
    @Override
    protected boolean executeVirtually(MethodInfo method, Vector localVariables,
            OperandStack callerStack) {
        boolean ret = DummyReturnTypeManager.getInstanceOf().executeVirtually(method, localVariables, callerStack);
        if (!ret) {
            currentlyMethodInExecution.add(new MethodCallInfo(method, (Vector) localVariables.clone()));
        }
        return ret;
    }

    @Override
    protected void workBeforeMethodExecuted(MethodInfo method,
            Vector localVariables, OperandStack callerStack,
            VerificationInstruction lastInstrExecuted) {
        super.workBeforeMethodExecuted(method, localVariables, callerStack, lastInstrExecuted);
        addMethodExecuted(method);
        FunctionsFlowRecorder.getInstanceOf().recordMethodCalled(method, localVariables);
        addNewThreadToBeStarted(method, localVariables, lastInstrExecuted);
    }

    @Override
    protected void workBeforeInstructionExecuted(Vector callingParameters,
            MethodInfo method,
            VerificationInstruction instrGoingToExecute,
            OperandStack operandStack,
            LocalVariables localVariables) throws Exception {
        super.workBeforeInstructionExecuted(callingParameters, method,
                instrGoingToExecute, operandStack, localVariables);
        getfieldAndLoadInstrContr.addRecord(method, callingParameters,
                (GCInstruction) instrGoingToExecute,
                (GCOperandStack) operandStack, (GCLocalVariables) localVariables);
        getfieldAndLoadInstrContr.fixOrderRelatedInstrs(method, callingParameters,
                (GCInstruction) instrGoingToExecute,
                (GCOperandStack) operandStack,
                (GCLocalVariables) localVariables);
        setFieldInstrDirty(method, callingParameters);
    }

    @Override
    protected void workAfterInstructionExecuted(MethodInfo method, Frame frame,
            Vector nextInstrIds,
            VerificationInstruction currentInstr,
            Vector allInstruction, Vector methodCallingParamters) throws Exception {
        //stopNewRefToPropogateToContructor(frame, currentInstr);

        super.workAfterInstructionExecuted(method, frame, nextInstrIds,
                currentInstr, allInstruction, methodCallingParamters);
        ((GCInstruction) currentInstr).saveLeavingFrame(frame);

        InvokeInstructionRecord invokeInstrRec = InvokeInstructionRecord.getInstanceOf();
        invokeInstrRec.addInvokeInstrRecord(method, methodCallingParamters, (GCInstruction) currentInstr);
    }

    private void setFieldInstrDirty(MethodInfo method,
            Vector methodCallingParamters) {

        MethodCallInfo currentMethodCallInfo = new MethodCallInfo(method, methodCallingParamters);
        HashSet<FieldRecord> fieldRecordSet = OrderManagerForFieldInstrs.getInstanceOf().getAndRemoveUnderExecMethodWithDirtyGetFieldInstr(currentMethodCallInfo);
        if (fieldRecordSet == null || fieldRecordSet.size() == 0) {
            return;
        }
        Iterator<FieldRecord> fieldRecordIt = fieldRecordSet.iterator();
        while (fieldRecordIt.hasNext()) {
            FieldRecord fieldRec = fieldRecordIt.next();
            fieldRec.getInstr().setChangeBit(true);
        }
    }

    @Override
    protected void workAfterMethodExecuted(MethodInfo method, Frame myFrame,
            OperandStack callerStack, HashSet<Type> returnTypes,
            Vector methodCallingParamerters) throws Exception {

        super.workAfterMethodExecuted(method, myFrame, callerStack,
                returnTypes, methodCallingParamerters);
        String methodStr = oracle.getMethodOrFieldString(method);
        if (methodStr.contains("fooBar")) {
        }
        //save the method status
        FunctionStateRecorder.getInstanceOf().addFunctionState(method, methodCallingParamerters,
                (GCOperandStack) callerStack);
        currentlyMethodInExecution.remove(new MethodCallInfo(method, methodCallingParamerters));
        DummyReturnTypeManager.getInstanceOf().updateReturnType(method, methodCallingParamerters, callerStack);
        //Miscellaneous.println("\n Next instruction record" + printNextInstrOffSetRecord(method));
    }

    private String printNextInstrOffSetRecord(MethodInfo method) {
        Vector instrVec = method.getCodeAtt().getInstructions();
        String ret = "\n";
        for (int loop = 0; loop < instrVec.size(); loop++) {
            GCInstruction instr = (GCInstruction) instrVec.elementAt(loop);
            Vector nextInstrRecord = instr.getNextInstrsToBeExecutedRecord();
            Iterator<GCInstruction> it = nextInstrRecord.iterator();
            while (it.hasNext()) {
                GCInstruction nextInstr = it.next();
                ret = ret + nextInstr.getOffSet() + "\n";
            }
        }
        return ret;
    }

    @Override
    protected boolean shouldMethodExecute(MethodInfo method, Vector localVariables,
            OperandStack callerStack) {

        //save the method status
        HashMap elem = FunctionStateRecorder.getInstanceOf().getFunctionState(method, localVariables);
        if (elem == null) {
            return true;
        }
        Type oldReturnType = FunctionStateRecorder.getInstanceOf().getFunctionReturnType(method, localVariables);
        if (oldReturnType != null) {
            //should be null for void functions
            callerStack.push(oldReturnType);
        }
        return false;
    }
}
